iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 23
1
Modern Web

從0.5開始的JavaScript系列 第 23

Day23 Promise 詳解(1/2)

  • 分享至 

  • xImage
  •  

我們在 Day5 提到了 JS 有非同步的機制,也因為這個機制,所以不會讓同步執行的 JS 阻塞,遇到 AJAXtimer 等等的操作,都會先丟到事件佇列中,然後再慢慢拉回執行。

但是當 callback function 一複雜起來,將會導致回調地域,也就是 callback function 中包了更多的 callback function,原意是想等待前面一件事情完成後,再接著做新的事情,但卻導致程式碼變成很深的巢狀,也喪失了可讀性與不好維護。

Promise

狀態

Promise 優化了非同步執行的流程語法,使用上也很簡單,

  1. 建立 Promise,處理 成功失敗 狀態
  2. 使用 then 串接
  3. 使用 catch 處理錯誤(失敗)

thencatch 的用法在昨日 Day22 已經展示,但上方第一點提到的成功失敗是什麼意思呢?

Promise 顧名思義就是「承諾」的意思,既然是承諾,必定會告知對方結果,像是某A先生與網美小花告白,小花是個有禮貌的人,不會直接無聲卡,所以她一定會回應某A先生,不論這個回應是好消息還是壞消息,而且這個狀態一旦回應,就不能再更改,也就是說網美小花只要接受某A先生,就不能夠反悔。

寫成程式碼大概就長這樣

var willTogether = function(money) {
    return new Promise(function (resolve, reject) {
      if(money > 100) {
          resolve("我也愛你,A先生");
      } else {
          reject(new Error('你是一個好人,但你值得更好的人'));
      }
    });
}

willTogether(10)
.then(function(val) {
    console.log(val);
})
.catch(function(err) {
    console.log(err);
});

從上方的範例中可以得知,Promise 總共有三個狀態

  1. pending: 等待中、還未給出回應。
  2. resolve: 完成/成功
  3. reject: 失敗

隔日某A先生撿到一些錢後...(純屬玩笑,請勿認真Q_Q)

chaining 一直 then 下去吧!

好啦,總之就是 then 會接收 resolve 的 value,catch 則處理 reject 狀態。

而每個 then 可以再回傳一個 Promise(或值),它的結果會傳入下一個 then,所以可以一直串下去,也不會寫成醜醜的回調地域

但是使用上需要注意,catch 如果是串在最後面,只要中途發生了 reject,將導致後面的 then 不會被執行,像是這樣:

willTogether(500)
.then(function(val) {
    console.log("A1", val); // A1 正常印出
	return willTogether(50);
})
.then(function(val) {
    console.log("A2", val); // A2 不會印出
	return willTogether(300);
})
.then(function(val) {
    console.log("A3", val); // A3 不會印出
	return willTogether(200);
})
.then(function(val){
	console.log("A4", val); // A4 不會印出
})
.catch(function(err) {
    console.log(err); // sorry
});

A1 如期印出,但是 A2A3A4 則不會印出,因為 willTogether(50) 回傳的狀態是 reject,所以直接被送到離它最近的 catch 處理,而 A2A3A4 也就不會被執行了。

如果改成這樣:

willTogether(500)
.then(function(val) {
    console.log("A1", val); // A1 正常印出
	return willTogether(50);
})
.catch(function(err) {
    console.log(err); // sorry
	return "keep going";
})
.then(function(val) {
    console.log("A2", val); // A2 keep going
	return willTogether(300);
})
.then(function(val) {
    console.log("A3", val); // A3 正常印出
	return willTogether(200);
})
.then(function(val){
	console.log("A4", val); // A4 正常印出
})
.catch(function(err) {
    console.log(err); // 不會印出
});

多在 willTogether(50) 串一個 catch 來收它的錯誤,則在印出錯誤訊息後,回傳 keep going 字串,繼續 then 執行下去,所以 A2 印出 keep going(不一定要回傳,不回傳下一個 then 會收到 undefined),之後的 A3、A4 則正常印出。

then vs catch

其實 catch 只是 then 的縮寫(語法糖)而已,then 完整寫法應該是這樣:

.then(function(val){
    // if resolve
    ...
}, function(err){
    // if reject
    ...
})

也就是其實 then 自己就可以處理 reject,不需要 catch,只是平常直接省略後面接收 reject 的函數,因為在沒有處理 reject 的情況下,每個 then 都寫出兩個函數來會讓程式碼很亂。

catch 則是 then 縮寫成這樣:

.then(undefined, function(err){
    // if reject
    ...
})

那我們把上面的程式碼改一下,把 A2 所在的那個 then 多加一個 reject function

willTogether(500)
.then(function(val) {
    console.log("A1", val); // A1 正常印出
	return willTogether(50);
})
.then(function(val) {
    console.log("A2", val); // A2 不會印出
	return willTogether(300); // 也不會回傳
}, function(err){
	console.log(err); // sorry
	return "keep going"; // 這個才會回傳
})
.then(function(val) {
    console.log("A3", val); // A3 keep going
	return willTogether(200);
})
.then(function(val){
	console.log("A4", val); // A4 正常印出
})
.catch(function(err) {
    console.log(err); // 不會印出
});

我們可以看到 A2 不會印出,因為前一個 Promise 回傳的狀態是 reject,所以 A2 這個 then 接收到後,是執行第二個 reject 那個函數,也就是印出錯誤訊息後,再回傳一個 keep going 的字串。

概念釐清

  1. resolve 不一定要寫入回傳值,也可以 resolve() 回傳 undefined

  2. 只要回傳的狀態不是 reject,就算當前是負責處理 reject 的 callback function,它 return 的值一樣會進入下一個 thenresolve function 中,所以範例中的 reject function,它回傳的 keep going 字串才會繼續被傳下去,而沒有進入下一個 thenreject function

範例中網美小花總是很快回應,所以看不出 Promise 的好處。如果今天的狀況是:

「網美小花對於每個追求者有不固定的思考時間,等待思考結束後才會給回應(接受、拒絕),但是追求者也因為很有禮貌,所以會等網美小花回應前一個追求者後,才會進行追求」

這樣的狀況,就複雜了很多。

還不會 Promise 之前的你,是不是準備在 callback function 中寫更多的 callback function 來處理每個追求者呢?

至此有沒有感受到 Promise 的力量了XD

Promise 的重點在於將非同步的 function 執行流程化,藉由 then 來順序的執行,catch 對於錯誤的處理機制也更方便。

Promise.resolve、Promise.reject

其實在簡單的情況下,可以不用建立一個完整的 Promise,可以使用

  • Promise.resolve
  • Promise.reject

像是上方的範例可以改寫成,

var willTogether = function(money) {
    if(money > 100) {
        return Promise.resolve("我也愛你,A先生");
    } else {
        return Promise.reject(new Error('你是一個好人,但你值得更好的人'));
    }
}

恭喜你已經會了 Promise 基本的用法,但是還沒完,明天會再講講 race、all、async、await,加油!

文章盡量使用淺白的方式來介紹,如果導致專有名詞表達不是那麼精準,還請包容,也可以留言幫忙補充,學習的路上有你有我~

今日的分享就到這,我們明天見/images/emoticon/emoticon51.gif


上一篇
Day22 AJAX(2): Fetch
下一篇
Day24 Promise 詳解(2/2)
系列文
從0.5開始的JavaScript30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言